import pygame
from pygame.locals import *
from general_utilities import *
from math import sqrt, atan, pi, ceil

class Glyph:
  def __init__(self,d,mouse_button_down_time,mouse_button_up_time,mouse_button_pos_series):
    self.down_time = mouse_button_down_time
    self.up_time = mouse_button_up_time
    self.duration = float(self.up_time - self.down_time)/1000 # in seconds
    self.pos_series = mouse_button_pos_series
    self.last_pos = mouse_button_pos_series[len(mouse_button_pos_series)-1]
    self.first_pos = mouse_button_pos_series[0]
    self.distance_first_to_last = mouse_distance(self.last_pos,self.first_pos)

def mouse_distance(right_down_1,right_down_2):
  return sqrt(pow(right_down_1[0]-right_down_2[0],2)+pow(right_down_1[1]-right_down_2[1],2))

def decimate(array,n):
  # return n points approx. evenly spaced from the array, including the first and last
  result = [array[0]]
  for i in range(1,n-1):
    result.append(array[int(ceil(i * len(array)/(n-1)))])
  result.append(array[len(array)-1])  
  return result

def difference(array):
  result = [0] * (len(array)-1)
  for i in range(1,len(array)):
    result[i-1] = (array[i][0] - array[i-1][0],array[i][1] - array[i-1][1])
  return result

def right_down_to_angle(right_down):
  # in degrees; zero or 360 is to the right
  down = right_down[1]
  right = right_down[0]
  if down>0 and right>0:
    return atan(float(down)/right)*180/pi
  elif down>0 and right==0:
    return 90
  elif down>0 and right<0:
    return 180+atan(float(down)/right)*180/pi
  elif down==0 and right<0:
    return 180
  elif down<0 and right<0:
    return 180+atan(float(down)/right)*180/pi
  elif down<0 and right==0:
    return 270
  elif down<0 and right>0:
    return 360+atan(float(down)/right)*180/pi
  elif down==0 and right>0:
    return 0

def is_click(glyph):
  if glyph.duration < 0.2 and glyph.distance_first_to_last<=10:
    return 1
  else:
    return 0
  
def is_loose_click(glyph):
  if glyph.duration < 0.4 and glyph.distance_first_to_last<=20:
    return 1
  else:
    return 0

def is_wiggly_line(glyph):
  #if not is_line(glyph,n_points=4,max_dev=90):
    #return 0
  #if not is_line(glyph,n_points=11,min_mean_dev=20):
    #return 0
  #return 1
  # let's try a different approach
  if glyph.distance_first_to_last<10:
    return 0
  diff = (glyph.last_pos[0]-glyph.first_pos[0],glyph.last_pos[1]-glyph.first_pos[1])
  length = sqrt(pow(diff[0],2) + pow(diff[1],2))
  denormed_diff = (diff[0] / length,diff[1] / length)
  deviations = []
  abs_deviations = []
  for i in range(len(glyph.pos_series)):
    this_right_down = glyph.pos_series[i]  
    destarted = (this_right_down[0]-glyph.first_pos[0],this_right_down[1]-glyph.first_pos[1])
    transformed_point = [(destarted[0]*denormed_diff[0]+destarted[1]*denormed_diff[1])/length,(destarted[0]*denormed_diff[1]-destarted[1]*denormed_diff[0])/length] # (0,0) is start point, (1,0) is end point)
    if transformed_point[0]<0 and transformed_point[1]<=0:
      this_deviation = -(pow(transformed_point[0],2) + pow(transformed_point[1],2))
    elif transformed_point[0]<0 and transformed_point[1]>0:
      this_deviation = (pow(transformed_point[0],2) + pow(transformed_point[1],2))
    elif transformed_point[0]>1 and transformed_point[1]<=0:
      this_deviation = -(pow(transformed_point[0]-1,2) + pow(transformed_point[1],2))
    elif transformed_point[0]>1 and transformed_point[1]>0:
      this_deviation = (pow(transformed_point[0]-1,2) + pow(transformed_point[1],2))
    elif transformed_point[1]<=0:
      this_deviation = -abs(transformed_point[1])
    else:
      this_deviation = abs(transformed_point[1])      
    deviations.append(this_deviation)
    abs_deviations.append(abs(this_deviation)) 
  if max(abs_deviations) < 0.4 and max(deviations)+-min(deviations)>0.2:
    return 1
  else:
    return 0
  
def is_straight_line(glyph):
  # return is_line(glyph,n_points=9,max_dev=25)
  # let's try a different approach
  if glyph.distance_first_to_last<10:
    return 0
  diff = (glyph.last_pos[0]-glyph.first_pos[0],glyph.last_pos[1]-glyph.first_pos[1])
  length = sqrt(pow(diff[0],2) + pow(diff[1],2))
  denormed_diff = (diff[0] / length,diff[1] / length)
  deviations = []
  abs_deviations = []
  for i in range(len(glyph.pos_series)):
    this_right_down = glyph.pos_series[i]  
    destarted = (this_right_down[0]-glyph.first_pos[0],this_right_down[1]-glyph.first_pos[1])
    transformed_point = [(destarted[0]*denormed_diff[0]+destarted[1]*denormed_diff[1])/length,(destarted[0]*denormed_diff[1]-destarted[1]*denormed_diff[0])/length] # (0,0) is start point, (1,0) is end point)
    if transformed_point[0]<0 and transformed_point[1]<=0:
      this_deviation = -(pow(transformed_point[0],2) + pow(transformed_point[1],2))
    elif transformed_point[0]<0 and transformed_point[1]>0:
      this_deviation = (pow(transformed_point[0],2) + pow(transformed_point[1],2))
    elif transformed_point[0]>1 and transformed_point[1]<=0:
      this_deviation = -(pow(transformed_point[0]-1,2) + pow(transformed_point[1],2))
    elif transformed_point[0]>1 and transformed_point[1]>0:
      this_deviation = (pow(transformed_point[0]-1,2) + pow(transformed_point[1],2))
    elif transformed_point[1]<=0:
      this_deviation = -abs(transformed_point[1])
    else:
      this_deviation = abs(transformed_point[1])      
    deviations.append(this_deviation)
    abs_deviations.append(abs(this_deviation)) 
  if max(abs_deviations) < 0.1:
    return 1
  else:
    return 0
  
#def is_line(glyph,n_points,max_dev=360,min_mean_dev=0):
  #if glyph.distance_first_to_last<10:
    #return 0
  #n_points = min(n_points,len(glyph.pos_series)/3)
  #chosen_points = decimate(glyph.pos_series,n_points)
  #diffs = difference(chosen_points)
  #angles = [0] * len(diffs)
  #some_up = 0
  #some_down = 0
  #some_left = 0
  #some_right = 0
  #for i in range(len(diffs)):
    #angles[i] = right_down_to_angle(diffs[i])
    #if abs(diffs[i][0]) + abs(diffs[i][1]) <= 6:
      #angles[i] = None
    #else:
      #if diffs[i][0] < 0:
        #some_up = 1
      #else:
        #some_down = 1
      #if diffs[i][1] > 0:
        #some_right = 1
      #else:
        #some_left = 1
  #if some_up and (not some_down) and some_right and some_left:
    #for i in range(len(angles)):
      #if angles[i] is not None and angles[i]<=90:
        #angles[i] = 360+angles[i]
  #elif some_up and some_down and some_right and some_left:
    #return 0
  #mean_angle = 0
  #n = 0  
  #for i in range(len(angles)):
    #if angles[i] is not None:
      #mean_angle = mean_angle + angles[i]
      #n = n + 1
  #mean_angle = mean_angle / n  
  #max_deviation = 0
  #mean_deviation = 0
  #for i in range(len(angles)):
    #if angles[i] is not None:
      #abs_deviation = abs(angles[i]-mean_angle)
      #max_deviation = max(max_deviation,abs_deviation)
      #mean_deviation = mean_deviation + abs_deviation / n
  #if max_deviation < max_dev and mean_deviation >= min_mean_dev:
    #return 1
  #else:
    #return 0

def is_cross(d,display,last_glyph,second_last_glyph):
  if second_last_glyph is None:
    return 0
  if not is_straight_line(last_glyph) or not is_straight_line(second_last_glyph):
    return 0
  if last_glyph.distance_first_to_last > 2*second_last_glyph.distance_first_to_last:
    return 0
  if last_glyph.distance_first_to_last < 0.5*second_last_glyph.distance_first_to_last:
    return 0  
  angle1 = right_down_to_angle((last_glyph.last_pos[0]-last_glyph.first_pos[0],last_glyph.last_pos[1]-last_glyph.first_pos[1]))
  angle2 = right_down_to_angle((second_last_glyph.last_pos[0]-second_last_glyph.first_pos[0],second_last_glyph.last_pos[1]-second_last_glyph.first_pos[1]))
  if not((abs(angle1-angle2)<120 and abs(angle1-angle2)>60) or (abs(angle1-angle2)>240 and abs(angle1-angle2)<300)):
    return 0
  map_pos_covered_1 = []
  for right_down in last_glyph.pos_series:
    this_map_pos = mouse_pos_to_map_pos(d,display,right_down)    
    if this_map_pos is not None:
      if not (this_map_pos in map_pos_covered_1):
        map_pos_covered_1.append(this_map_pos)
  for right_down in second_last_glyph.pos_series:  
    this_map_pos = mouse_pos_to_map_pos(d,display,right_down)  
    if this_map_pos is not None:
      if this_map_pos in map_pos_covered_1:
        return 1
  return 0

def intersection_of_cross(d,last_glyph,second_last_glyph):
  # Returns a Pos - does not check that it is a cross, do this first by calling is_cross
  map_pos_covered_1 = []
  for right_down in last_glyph.pos_series:
    this_map_pos = mouse_pos_to_map_pos(d,display,right_down) 
    if this_map_pos is not None:
      if not (this_map_pos in map_pos_covered_1):
        map_pos_covered_1.append(this_map_pos)
  for right_down in second_last_glyph.pos_series:  
    this_map_pos = mouse_pos_to_map_pos(d,display,right_down)    
    if this_map_pos is not None:
      if this_map_pos in map_pos_covered_1:
        return this_map_pos
  return None
  
def is_circle(glyph):
  # return 1 if it is a circle over the map, or 0 else
  if len(glyph.pos_series)<9:
    return 0
  if glyph.distance_first_to_last>15:
    return 0
  if is_clockwise(glyph) or is_anticlockwise(glyph):
    return 1
  return 0

def is_clockwise(glyph):
  nine_points = decimate(glyph.pos_series,9)
  diffs = difference(nine_points)
  angles = []
  crossings = 0
  for i in range(len(diffs)):
    if abs(diffs[i][0]) + abs(diffs[i][1])>=4:
      angles.append(right_down_to_angle(diffs[i]))
  for i in range(len(angles)):
    if i>0:
      ok = 0
      if angles[i]<angles[i-1]+130 and angles[i]>angles[i-1]:
        ok = 1
      elif angles[i]<130 and angles[i-1]>230 and angles[i]+360-angles[i-1] < 130 and crossings==0:
        ok = 1
        crossings = 1
      if ok==0:
        return 0
  return 1

def is_anticlockwise(glyph):
  nine_points = decimate(glyph.pos_series,9)
  diffs = difference(nine_points)
  angles = []
  crossings = 0
  for i in range(len(diffs)):
    if abs(diffs[i][0]) + abs(diffs[i][1])>=4:
      angles.append(right_down_to_angle(diffs[i]))
  for i in range(len(angles)):
    if i>0:
      ok = 0
      if angles[i-1]<angles[i]+130 and angles[i-1]>angles[i]:
        ok = 1
      elif angles[i-1]<130 and angles[i]>230 and angles[i-1]+360-angles[i] < 130 and crossings==0:
        ok = 1
        crossings = 1
      if ok==0:
        return 0
  return 1

def centre_of_pos_set(d,glyph):
  # return a Pos
  # typically a circle, could be anything I suppose - not tested for circularity!
  sum_right = 0
  sum_down = 0
  n = 0
  for right_down in glyph.pos_series:
    this_map_pos = mouse_pos_to_map_pos(d,display,right_down)
    if this_map_pos is not None:
      sum_right = sum_right + this_map_pos.right
      sum_down = sum_down + this_map_pos.down
      n = n + 1
  result = this_map_pos.copy()
  result.down = int(floor(0.5+sum_down/n))
  result.right = int(floor(0.5+sum_right/n))      
  return result

def center_mouse_pos(glyph):
  # just an arithmetic average of the points
  sum_down = 0
  sum_right = 0
  n = 0
  for right_down in glyph.pos_series:
    sum_down = sum_down + right_down[1]
    sum_right = sum_right + right_down[0]
    n = n + 1
  return (sum_right/n, sum_down/n)

def all_map_pos_in_circle(d,display,glyph):
  # return a [Pos] of all map pos's in the circle (including ones only mostly in) or None
  map_pos_crossed = []
  min_down = 1e10
  max_down = 0
  for right_down in glyph.pos_series:
    this_map_pos = mouse_pos_to_map_pos(d,display,right_down)
    if this_map_pos is not None:
      if not(this_map_pos in map_pos_crossed):
        map_pos_crossed.append(this_map_pos)
        if this_map_pos.down < min_down:
          min_down = this_map_pos.down
        if this_map_pos.down > max_down:
          max_down = this_map_pos.down
  result = []
  for i in range(min_down,max_down+1):
    min_right = 50
    max_right = 0
    for map_pos in map_pos_crossed:
      if map_pos.down == i:
        if map_pos.right < min_right:
          min_right = map_pos.right
        if map_pos.right > max_right:
          max_right = map_pos.right        
        for j in range(min_right,max_right+1):
          inclusion = map_pos.copy()
          inclusion.right = j
          result.append(inclusion)
  return result

def mouse_pos_to_map_pos(d,display,mouse_pos):
  # return a Pos or None
  down_right = display.get_mouse_down_right(mouse_pos)
  if down_right is None:
    return None
  else: 
    map_pos = d.get_active_pos().copy()
    map_pos.down = down_right[0]
    map_pos.right = down_right[1]
    return map_pos

def map_pos_to_mouse_pos(d,display,map_pos):
  # return a (down,right)
  mouse_pos = [0,0]
  mouse_pos[1] = (map_pos.down+0.5)*display.iconsize + display.map.top + 1
  mouse_pos[0] = (map_pos.right+0.5)*display.iconsize + display.map.left + 1
  return mouse_pos[0],mouse_pos[1]
      
def quadrant_relative_to_you(d,display,mouse_pos):
  # return DIR_UP, DIR_DOWN, DIR_RIGHT, DIR_LEFT or None
  # If on a diagonal, result is arbitrary
  character_mouse_pos = map_pos_to_mouse_pos(d,display,d.get_active_pos()) 
  right_of_you = mouse_pos[0] - character_mouse_pos[0]
  down_of_you = mouse_pos[1] - character_mouse_pos[1]
  if right_of_you >= display.iconsize/2 and abs(right_of_you) >= abs(down_of_you):
    return DIR_RIGHT
  elif right_of_you <= -display.iconsize/2 and abs(right_of_you) >= abs(down_of_you):
    return DIR_LEFT
  elif down_of_you <= -display.iconsize/2 and abs(right_of_you) < abs(down_of_you):
    return DIR_UP
  elif down_of_you >= display.iconsize/2 and abs(right_of_you) < abs(down_of_you):
    return DIR_DOWN
  else:
    return None
  
def octant_relative_to_you(d,display,mouse_pos):
  # return a DIR_ code or None
  character_mouse_pos = map_pos_to_mouse_pos(d,display,d.get_active_pos()) 
  down = mouse_pos[1] - character_mouse_pos[1]
  right = mouse_pos[0] - character_mouse_pos[0]
  if abs(right)+abs(down) <= display.iconsize/2:
    return None
  if right==0:
    if down>0:
      return DIR_DOWN
    elif down<0:
      return DIR_UP
  angle = atan(float(down)/float(right))*360/(2*pi)
  if right<0:
    angle = angle+180
  if angle>337.5 or angle<=22.5:
    return DIR_RIGHT
  elif angle<=67.5:
    return DIR_SE
  elif angle<=112.5:
    return DIR_DOWN
  elif angle<=157.5:
    return DIR_SW
  elif angle<=202.5:
    return DIR_LEFT
  elif angle<=247.5:
    return DIR_NW
  elif angle<=292.5:
    return DIR_UP
  elif angle<=337.5:
    return DIR_NE
  return None
  
def is_on_edge_of_map(d,display,mouse_pos):
  # return a DIR_ code or None
  # If in a corner, arbitrary which edge is chosen
  down_right = display.get_mouse_down_right(mouse_pos)
  if down_right is None:
    return None
  else: 
    room = d.l.room_of(d.get_active_pos())
    if right==0:
      return DIR_LEFT
    elif right==room.width-1:
      return DIR_RIGHT
    elif down==0:
      return DIR_UP
    elif down==room.height-1:
      return DIR_DOWN
    else:
      return None
    
def is_in_corner_of_map(d,display,mouse_pos):
  # return a DIR_ code or None
  down_right = display.get_mouse_down_right(mouse_pos)
  if down_right is None:
    return None
  else: 
    room = d.l.room_of(d.get_active_pos())
    if right==0 and down==0:
      return DIR_NW
    elif right==room.width-1 and down==0:
      return DIR_NE
    elif right==0 and down==room.height-1:
      return DIR_SW
    elif right==room.width-1 and down==room.height-1:
      return DIR_SE
    else:
      return None  

def is_on_health_bar(d,display,mouse_pos):
  if display.hp_rect.collidepoint(mouse_pos):
    return 1
  else:
    return 0

def is_on_magic_bar(d,display,mouse_pos):
  if display.mp_rect.collidepoint(mouse_pos):
    return 1
  else:
    return 0
  
def is_on_energy_bar(d,display,mouse_pos):
  if display.energy_rect.collidepoint(mouse_pos):
    return 1
  else:
    return 0

def is_on_minimap_button(d,display,mouse_pos):
  if display.minimap_rect.collidepoint(mouse_pos):
    return 1
  else:
    return 0

def is_on_melee_weapon(d,display,mouse_pos):
  if display.melee_weapon_rect.collidepoint(mouse_pos):
    return 1
  else:
    return 0
  
def is_on_missile_weapon(d,display,mouse_pos):
  if display.missile_weapon_rect.collidepoint(mouse_pos):
    return 1
  else:
    return 0

def is_double_click(latest_glyph,second_latest_glyph):
  if second_latest_glyph is None:
    return 0
  if not is_click(latest_glyph) or not is_click(second_latest_glyph):
    return 0
  if latest_glyph.down_time - second_latest_glyph.up_time < 200:
    if mouse_distance(second_latest_glyph.last_pos,latest_glyph.first_pos) < 5:
      return 1
  return 0

def is_triple_click(latest_glyph,second_latest_glyph,third_latest_glyph):
  if second_latest_glyph is None or third_latest_glyph is None:
    return 0
  if not is_click(latest_glyph) or not is_click(second_latest_glyph) or not is_click(third_latest_glyph):
    return 0
  if latest_glyph.down_time - second_latest_glyph.up_time < 200:
    if mouse_distance(second_latest_glyph.last_pos,latest_glyph.first_pos) < 5:
      if second_latest_glyph.down_time - third_latest_glyph.up_time < 200:
        if mouse_distance(third_latest_glyph.last_pos,second_latest_glyph.first_pos) < 5:
          return 1
  return 0

